/**
 * Copyright (c) 2011-2014, OpenIoT
 *
 * This file is part of OpenIoT.
 *
 * OpenIoT is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * OpenIoT is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with OpenIoT. If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact: OpenIoT mailto: info@openiot.eu
 */
package org.openiot.cupus.artefact;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.openiot.cupus.common.Triplet;
import org.openiot.cupus.common.enums.Operator;

/**
 * This class is a conjuction-of-predicates implementation of a boolean
 * subscription. A predicate is represented by a Triplet object, an they are
 * grouped by the attribute over which they are defined in a map. Each map entry
 * is a list of predicated related to that attribute.
 *
 * @author Eugen
 */
public class TripletAnnouncement extends Announcement implements Serializable {

    private static final long serialVersionUID = 1L;

    private HashMap<String, Set<Triplet>> predicateMap = new HashMap<String, Set<Triplet>>();

    /**
     * This map is for internal use in matching only. It should be null outside
     * of the broker/matcher to minimize the serialised size of the subscription
     * for sending through networks. The keys are the identifiers(names) of
     * string attributes that have predicates defined over them in this
     * subscription, and the value is a triplet that holds the information about
     * the lowest and highest index (BETWEEN) of the allowed values for the
     * attribute that match the predicates over the attribute in this
     * subscription. The information is used in the CdirBucket class to check
     * for enclosement in an efficient way and it should be set by the
     * Attributes class on initial check when the subscription enters the
     * broker.
     */
    public HashMap<String, Triplet> stringAttributeBorders = null;

    public TripletAnnouncement(long validity, long startTime) {
        super(validity, startTime);
    }

    public void addNumericalPredicate(String name) {
        addPredicate(new Triplet(name, Double.NEGATIVE_INFINITY, Operator.GREATER_OR_EQUAL));
    }

    public void addNumericalPdredicate(String name, double minValue,
            double maxValue) {
        addPredicate(new Triplet(name, new Double[]{minValue, maxValue},
                Operator.BETWEEN));
    }

    public void addNumericalPdredicate(String name, double value,
            Operator operator) {
        addPredicate(new Triplet(name, value, operator));
    }

    public void addTextualPdredicate(String name) {
        addPredicate(new Triplet(name, "", Operator.CONTAINS_STRING));
    }

    public void addTextualPdredicate(String name, String value,
            Operator operator) {
        addPredicate(new Triplet(name, value, operator));
    }

    /**
     * Adds a predicate (constraint) to this subscription.
     *
     * @param triplet (attribute(key), value, operator)
     */
    private void addPredicate(Triplet triplet) {
        Set<Triplet> set = predicateMap.get(triplet.getKey());
        if (set != null) {
            set.add(triplet);
        } else {
            set = new HashSet<Triplet>();
            set.add(triplet);
            predicateMap.put(triplet.getKey(), set);
        }
    }

    public Set<String> attributes() {
        return predicateMap.keySet();
    }

    public Set<Triplet> attributePredicates(String attribute) {
        return predicateMap.get(attribute);
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (other instanceof TripletAnnouncement) {
            TripletAnnouncement announcement = (TripletAnnouncement) other;
            if (super.equals(announcement)) {
                return true;
            }
            if (this.validity == announcement.validity
                    && this.startTime == announcement.startTime) {
                return predicateMap.equals(announcement.predicateMap);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 71 * hash + (predicateMap != null ? predicateMap.hashCode() : 0);
        return hash;
    }

    /**
     * Method for checking whether subscription covers other subscription.
     *
     * @param sub subscription to confront
	 *
     */
    @Override
    public boolean coversSubscription(Subscription sub) {
        if (sub instanceof TripletSubscription) {
            TripletSubscription tripSub = (TripletSubscription) sub;
            for (String subAttribute : tripSub.attributes()) {
                if (this.predicateMap.get(subAttribute) == null) {
                    return false;
                } else {
                    Set<Triplet> subTriplets = tripSub.attributePredicates(subAttribute);
                    Set<Triplet> annTriplets = this.attributePredicates(subAttribute);
                    for (Triplet sT : subTriplets) {
                        boolean satisfaction = false;
                        for (Triplet aT : annTriplets) {
                            if (aT.partiallyCovers(sT)) {
                                satisfaction = true;
                                break;
                            }
                        }
                        if (!satisfaction) {
                            return false;
                        }
                    }
                }
            }
            return true;
        } else if (sub instanceof ActiveSubscription) {
            return this.coversSubscription(((ActiveSubscription) sub)
                    .getSubscription());
        /*} else if (sub instanceof MemorySubscription) {
            return this.coversSubscription(((MemorySubscription) sub).getSubscription());*/
        } else if (sub instanceof TripletTopKWSubscription) {
            TripletTopKWSubscription tripTopKW = (TripletTopKWSubscription) sub;

            for (Triplet subscriptionTriplet : tripTopKW.getData()) {
                if (!this.attributes().contains(subscriptionTriplet.getKey())) {
                    return false;
                }
            }
            return true;

        }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder("( ");
        for (String key : predicateMap.keySet()) {
            for (Triplet triplet : predicateMap.get(key)) {
                str.append(triplet.toString() + " , ");
            }
        }
        str.replace(str.length() - 2, str.length(), ")"); // replacing last ", " with ")"
        return str.toString();
    }

}
